package org.wattdepot.simulation.oahugrid.demandcurve;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import org.wattdepot.simulation.oahugrid.main.batch.BatchSimulation;

/**
 * Superclass for all DailyDemandCurve instances. 
 * @author Philip Johnson
 */
public abstract class DailyDemandCurve {
  
  /** Provides the required MW value for each 15 minute period during this day. */
  protected Integer[] demandCurve = new Integer[96];
  /** The day to be associated with this demand curve.  Hours, minutes, seconds ignored. */
  private GregorianCalendar date;
  /** Separates the day into 'zones' where different kinds of power are used. */
  public enum PeakType { OFFPEAK, SHOULDERPEAK, PRIORITYPEAK };
  /** A random number generator. */
  private final Random random = new Random(0);
  
  /** A cache that stores the peak MW for this daily demand curve. */
  private Map<PeakType, Integer> maxPeakCache = new HashMap<PeakType, Integer>();
  
  /**
   * Saves the day (represented by a Calendar object) to be associated with this Profile.
   * @param date The date for this demand Curve.
   */
  DailyDemandCurve(GregorianCalendar date) {
    this.date = date;
  }
  
  /**
   * Given a period between 0 and 95, returns the associated timestamp representing the start
   * of the interval.
   * Throws a RuntimeException if period is outside of range.  
   * @param period The period (15 minute interval) for this day.
   * @return The timestamp for this period.
   */
  public GregorianCalendar getTimestamp(int period) {
    validatePeriod(period); 
    // Create a timestamp, initialize to the date and zero out hours, minutes, seconds, millis.
    GregorianCalendar timestamp = new GregorianCalendar();
    timestamp.setTime(this.date.getTime());
    timestamp.set(Calendar.HOUR_OF_DAY, 0);
    timestamp.set(Calendar.MINUTE, 0);
    timestamp.set(Calendar.SECOND, 0);
    timestamp.set(Calendar.MILLISECOND, 0);
    // Now increment for the associated interval.
    timestamp.add(Calendar.MINUTE, period * 15);
    return timestamp;
  }
  
  /**
   * Returns a string containing 96 lines with the demand curve for this day.
   * @return The string representing the demand curve.
   */
  @Override
  public String toString() {
    // Create a timestamp, initialize to the date and zero out hours, minutes, seconds, millis.
    StringBuffer buff = new StringBuffer();
    SimpleDateFormat sdf = new SimpleDateFormat(BatchSimulation.DATE_FORMAT, Locale.US);
    
    for (int i = 0; i < 96; i++) {
      GregorianCalendar timestamp = getTimestamp(i);
      buff.append(String.format("%d %s : %d%n", i, 
          sdf.format(timestamp.getTime()), demandCurve[i]));
      timestamp.add(Calendar.MINUTE, 15);
    }
    return buff.toString();
  }
  
  /**
   * Returns a PeakType, which gives guidance on how to assign plants.
   * @param period The period of the day.
   * @return The PeakType for this period.
   */
  public abstract PeakType getPeakType(int period);
  
  /**
   * Returns the amount of MW required by the grid during this period. 
   * @param period The period of time.
   * @return The amount of MW required by the grid.
   */
  public int getGridMW(int period) {
    validatePeriod(period); 
    return this.demandCurve[period];
  }

  /**
   * Throws a RuntimeException if the passed period is not between 0 and 95, otherwise does
   * nothing.
   * @param period The period.
   */
  private void validatePeriod(int period) {
    if ((period < 0) || (period > 95)) {
      throw new RuntimeException("Illegal period: " + period);
    }
  }
  
  /**
   * Returns the maximum MW required by this demand curve for any given period. 
   * @return The maximum MW required.
   */
  public int getMaxMW() {
    int maxMW = 0;
    for (int i = 0; i < 96; i++) {
      if (demandCurve[i] > maxMW) {
        maxMW = demandCurve[i];
      }
    }
    return maxMW;
  }

  /**
   * Adds random variation to the demand curve by "jiggling" each period by plus or minus
   * a random number between -jiggle and +jiggle.
   * @param jiggle The amount to "jiggle".
   * @return The updated DailyDemandCurve.
   */
  public DailyDemandCurve addRandomVariation(int jiggle) {
    for (int i = 0; i < 96; i++) {
      // Determine the number to increment/decrement our demand by.
      int num = random.nextInt(jiggle);
      // make it positive or negative.
      int signedNum = (random.nextBoolean() ? num : (num * -1));
      // now jiggle the demand curve.
      demandCurve[i] = demandCurve[i] + signedNum;
    }
    return this;
  }

  /**
   * Returns the maximum MW used during the passed PeakType. Caches the value once computed.
   * @param type The PeakType of interest.
   * @return The maximum MW used during the given PeakType.
   */
  public int getPeakMaxMW(PeakType type) {
    // Update the cache if we don't have an entry for this type.
    if (!this.maxPeakCache.containsKey(type)) {
      this.maxPeakCache.put(type, 0);
      for (int i = 0; i < 96; i++) {
        if ((this.getPeakType(i) == type) && (demandCurve[i] > maxPeakCache.get(type))) {
          maxPeakCache.put(type, demandCurve[i]);
        }
      }
    }
    // Now return the maximum MW for this type.
    return maxPeakCache.get(type);
  }
}
